{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# IEEEP370 Deembedding\n",
    "## Table of Content\n",
    "* [Single-ended](#Single-ended)\n",
    "  * [Single-Ended simulation of 2xThru, DUT and Fixture-DUT-Fixture](#Single-Ended-simulation-of-2xThru,-DUT-and-Fixture-DUT-Fixture)\n",
    "  * [Single-Ended S-parameters quality checking](#Single-Ended-S-parameters-quality-checking)\n",
    "  * [IEEEP370_SE_NZC_2xthru without impedance correction](#IEEEP370_SE_NZC_2xthru-without-impedance-correction)\n",
    "  * [IEEEP370_SE_ZC_2xthru with impedance correction](#IEEEP370_SE_ZC_2xThru-with-impedance-correction)\n",
    "  * [Single Ended Comparison with AICC De-Embedding Utility](#Single-Ended-Comparison-with-AICC-De-Embedding-Utility)\n",
    "* [Mixed mode](#Mixed-mode)\n",
    "  * [Mixed mode simulation of 2xThru, DUT and Fixture-DUT-Fixture](#Mixed-mode-simulation-of-2xThru,-DUT-and-Fixture-DUT-Fixture)\n",
    "  * [Mixed mode S-parameters quality checking](#Mixed-mode-S-parameters-quality-checking)\n",
    "  * [IEEEP370_MM_NZC_2xthru without impedance correction](#IEEEP370_MM_NZC_2xThru-without-impedance-correction)\n",
    "  * [IEEEP370_MM_ZC_2xthru with impedance correction](#IEEEP370_MM_ZC_2xThru-with-impedance-correction)\n",
    "  * [Mixed mode Comparison with AICC De-Embedding Utility](#Mixed-mode-comparison-with-AICC-De-Embedding-Utility)\n",
    "\n",
    "\n",
    "## Introduction\n",
    "\n",
    "The [IEEE 370-2020 standard](https://standards.ieee.org/ieee/370/6165/) (_Standard for Electrical Characterization of Printed Circuit Board and Related Interconnects at Frequencies up to 50 GHz_) provides good practices for ensuring the quality of measured data for high-frequency electrical interconnects at frequencies up to 50 GHz. It recommends methods and processes for ensuring the accuracy and consistency of measured data for signals with frequency content up to 50 GHz, in particular for removing test fixture and instrumentation effects.\n",
    "\n",
    "In the examples below, and following the standard proposal on labeling, a 2-port DUT has one fixture attached to each of its ports. The fixture attached to the single-ended DUT port 1 is labeled as FIX-1. The fixture attached to the single-ended DUT port 2 is labeled as FIX-2. The cascade of the DUT and fixtures on both ends is named the composite structure or the FIX-DUT-FIX structure.\n",
    "\n",
    "<img src=\"fix-dut-fix.svg\" width=\"400\"/>\n",
    "\n",
    "The combination of the two fixtures, such as FIX-1 connected to FIX-2, is referred to as a FIX-FIX structure or a *2x-Thru*.\n",
    "\n",
    "<img src=\"fix-fix.svg\" width=\"270\"/>\n",
    "\n",
    "The purpose of this notebook is to illustrate the `scikit-rf` implementation of the deembedding methods proposed within the  IEEE P370 standard to remove the test fixture effects."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, let's make the necessary Python import statements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "from scipy.constants import c, pi\n",
    "\n",
    "import skrf as rf\n",
    "from skrf.calibration import (\n",
    "    IEEEP370,\n",
    "    IEEEP370_FD_QM,\n",
    "    IEEEP370_FER,\n",
    "    IEEEP370_TD_QM,\n",
    "    IEEEP370_MM_NZC_2xThru,\n",
    "    IEEEP370_MM_ZC_2xThru,\n",
    "    IEEEP370_SE_NZC_2xThru,\n",
    "    IEEEP370_SE_ZC_2xThru,\n",
    ")\n",
    "from skrf.media import DefinedGammaZ0, MLine\n",
    "\n",
    "rf.stylely()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Single-ended <a class=\"anchor\" id=\"Single-ended\"></a>\n",
    "The algorithms can be used with 2-port single-ended networks.\n",
    "\n",
    "### Single-Ended simulation of 2xThru, DUT and Fixture-DUT-Fixture <a class=\"anchor\" id=\"Single-Ended-simulation-of-2xThru,-DUT-and-Fixture-DUT-Fixture\"></a>\n",
    "We use `scikit-rf` `MLine` media to simulate microstrip line artifacts. This is a convenient way to generate synthetic S-parameter networks without leaving Python to operate another software.\n",
    "\n",
    "* `dut` is a a Beatty structure with a 3xWidth microstrip section connected left and right by two uniform 1xWidth microstrip lines.\n",
    "* `fdf` is FIX-DUT-FIX, prolonging the DUT left and right with a microstrip line and a planar-to-coaxial connector.\n",
    "* `s2xthru` is FIX-FIX and is the cascade of the two prolonging lines and connectors without the DUT. For example purpose, the width of the lines is altered by 8% to show the effect of impedance mismatch on the deembedding process.\n",
    "\n",
    "The target is to get the left and right fixtures models and retrieve the DUT by deembedding them from FIX-DUT-FIX.\n",
    "\n",
    "<img src=\"ieeep370deembedding/2x-Thru.svg\" width=\"700\"/>\n",
    "<img src=\"ieeep370deembedding/FIX-DUT-FIX.svg\" width=\"800\"/>\n",
    "\n",
    "The microstrip lines are inspired by [this example](./Time domain reflectometry, measurement vs simulation.ipynb)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "directory = 'ieeep370deembedding/'\n",
    "freq = rf.Frequency(10e-3, 10, 1000, 'GHz')\n",
    "W   = 3.00e-3\n",
    "H   = 1.55e-3\n",
    "T   = 50e-6\n",
    "ep_r = 4.459\n",
    "tanD = 0.0183\n",
    "f_epr_tand = 1e9\n",
    "\n",
    "# microstrip segments\n",
    "MSL1 = MLine(frequency=freq, z0_port=50, w=W, h=H, t=T,\n",
    "        ep_r=ep_r, mu_r=1, rho=1.712e-8, tand=tanD, rough=0.15e-6,\n",
    "        f_low=1e3, f_high=1e12, f_epr_tand=f_epr_tand,\n",
    "        diel='djordjevicsvensson', disp='kirschningjansen')\n",
    "\n",
    "# capacitive 3 x width Beatty structure\n",
    "MSL2 = MLine(frequency=freq, z0_port=50, w=3*W, h=H, t=T,\n",
    "        ep_r=ep_r, mu_r=1, rho=1.712e-8, tand=tanD, rough=0.15e-6,\n",
    "        f_low=1e3, f_high=1e12, f_epr_tand=f_epr_tand,\n",
    "        diel='djordjevicsvensson', disp='kirschningjansen')\n",
    "\n",
    "# microstrip segment with a 10% variation of width\n",
    "MSL3 = MLine(frequency=freq, z0_port=50, w=0.92*W, h=H, t=T,\n",
    "        ep_r=ep_r, mu_r=1, rho=1.712e-8, tand=tanD, rough=0.15e-6,\n",
    "        f_low=1e3, f_high=1e12, f_epr_tand=f_epr_tand,\n",
    "        diel='djordjevicsvensson', disp='kirschningjansen')\n",
    "\n",
    "# connector SMA edge PCB mount soldered\n",
    "CONN = DefinedGammaZ0(frequency=freq, gamma = 1j* 2 * pi * freq.f / c, z0_port = 50)\n",
    "z_conn = 53.2    # ohm, connector impedance\n",
    "d_conn = 50      # ps,   connector discontinuity delay\n",
    "connector = CONN.line(50, 'ps', z0 = z_conn)\n",
    "\n",
    "# building DUT\n",
    "dut =    MSL1.line(20e-3, 'm') \\\n",
    "      ** MSL2.line(20e-3, 'm') \\\n",
    "      ** MSL1.line(20e-3, 'm')\n",
    "dut.name = 'DUT'\n",
    "\n",
    "# building FIXTURE-DUT-FIXTURE\n",
    "thru1 = MSL1.line(25e-3, 'm')\n",
    "thru3 = MSL3.line(25e-3, 'm')\n",
    "fdf     = connector ** thru1 ** dut ** thru1 ** connector\n",
    "fdf.name = 'FIX-DUT-FIX'\n",
    "\n",
    "# building FIXTURE-FIXTURE with a 8% width variation from FIXTURE-DUT-FIXTURE(2xthru)\n",
    "s2xthru = connector ** thru3 ** thru3 ** connector\n",
    "s2xthru.name = '2x-Thru'\n",
    "\n",
    "# extrapolate to DC for time step\n",
    "dut_dc = IEEEP370.extrapolate_to_dc(dut)\n",
    "fdf_dc = IEEEP370.extrapolate_to_dc(fdf)\n",
    "s2xthru_dc = IEEEP370.extrapolate_to_dc(s2xthru)\n",
    "# set True to write .s2p files\n",
    "if False:\n",
    "    s2xthru.write_touchstone(directory + 'se_2xthru_nonoise.s2p')\n",
    "    fdf.write_touchstone(directory + 'se_fdf_nonoise.s2p')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the figure below, the time shift of DUT caused by the fixtures is visible on FIX-DUT-FIX trace. The 2x-Thru trace shows the awaited impedance mismatch with the FIX-DUT-FIX. The connectors add an impedance bump at the ends of 2x-Thru and FIX-DUT-FIX impedance step response. The reference DUT is the Beatty structure alone, without fixtures.\n",
    "\n",
    "The `_dc` networks were extrapolated to DC for the time-domain step plot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "\n",
    "axs[0].set_title('Time Step')\n",
    "dut_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "fdf_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "s2xthru_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "axs[0].set_xlim((-1.5, 2))\n",
    "axs[0].legend(loc = 'lower left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "dut.plot_s_db(0, 0, ax = axs[1])\n",
    "fdf.plot_s_db(0, 0, ax = axs[1])\n",
    "s2xthru.plot_s_db(0, 0, ax = axs[1])\n",
    "axs[1].legend(loc = 'lower left')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single-Ended S-parameters quality checking <a class=\"anchor\" id=\"Single-Ended-S-parameters-quality-checking\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, check the quality metrics of the data to ensure they do not violate passivity, reciprocity, or causality. Initial quality checking in the frequency domain results in percentage scores and is informative only. The results are evaluated as good, acceptable, inconclusive or poor. Inconclusive or poor results may indicate the need to recalibrate the VNA and perform the measurements again.\n",
    "\n",
    "This quick check may raise potential issues that question measurement, simulation, or de-embedding process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fd_qm = IEEEP370_FD_QM()\n",
    "print(s2xthru.name)\n",
    "qm_2xthru = fd_qm.check_se_quality(s2xthru)\n",
    "fd_qm.print_qm(qm_2xthru)\n",
    "print(fdf.name)\n",
    "qm_fdf = fd_qm.check_se_quality(fdf)\n",
    "fd_qm.print_qm(qm_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the results are in the best class, which is not surprising for synthetic data.\n",
    "\n",
    "Using the `verbose = True` parameter, it is possible to get an insight into the checking process. The causality check verifies that the complex S-parameters rotate clockwise in the complex plane. The passivity check verifies that the 2-Norm of S-parameters is smaller or equal to 1 at each frequency. The reciprocity check integrates the absolute difference between $S_{ij}$ and $S_{ji}$ pairs. For further details, refer to IEEE 370-2020."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qm_2xthru = fd_qm.check_se_quality(s2xthru, verbose = True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Application-based quality checking in the time domain targets interconnect applications with specified data rate and rise time. It is an advanced process that targets giving a physical estimation in millivolt based on peak distortion analysis between the input data and ideal re-constructed data.\n",
    "\n",
    "If necessary, the original S-parameters are extrapolated to a frequency of three times the desired data rate. Causal, passive, and reciprocal models are reconstructed and stimulated by a pulse. The differences between the original extrapolated signal and the ideal responses in the time domain are integrated on unit intervals to give metrics in millivolts. The results are evaluated as good, acceptable, inconclusive or poor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "td_qm = IEEEP370_TD_QM(1e9, # bps\n",
    "                       32,  # unit interval samples number\n",
    "                       0.4, # rise-time from 20% to 80% in fraction of width\n",
    "                       1,   # gaussian pulse\n",
    "                       1,   # constant extrapolation\n",
    "                       verbose = False)\n",
    "print(s2xthru.name)\n",
    "qm_2xthru = td_qm.check_se_quality(s2xthru)\n",
    "td_qm.print_qm(qm_2xthru)\n",
    "print(fdf.name)\n",
    "qm_fdf = td_qm.check_se_quality(fdf)\n",
    "td_qm.print_qm(qm_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the results are in the best class, which is not surprising for synthetic data.\n",
    "\n",
    "Using the `verbose = True` parameter, it is possible to get an insight into the checking process. The extrapolation, the pulse, and the time responses of the original and the causality-enforced models are plotted. For further details, refer to IEEE 370-2020. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qm_2xthru = td_qm.check_se_quality(s2xthru, verbose = True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before exercising the deembedding algorithms, the compliance of input data with IEEE 370 fixture electrical requirements (FER) shall be verified."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fer = IEEEP370_FER()\n",
    "fig = fer.plot_fd_se_fer(s2xthru)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = fer.plot_td_se_fer(s2xthru, fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The performance of the 2x-Thru is deemed class B compliant for frequencies below 10 GHz. This is due to the artificial impedance mismatch between 2x-Thru and FIX-DUT-FIX that fails the FER5 A limit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### IEEEP370_SE_NZC_2xthru without impedance correction <a class=\"anchor\" id=\"IEEEP370_SE_NZC_2xthru-without-impedance-correction\"></a>\n",
    "This method takes the 2x-Thru as input. It is a simple and efficient bisection algorithm that cannot correct for the difference of impedance between the lines of FIX-FIX and FIX-DUT-FIX. This unwanted difference should be minimized. It can appear because of materials inhomogeneities, manufacturing processes, or if the artifacts are not built on the same board.\n",
    "\n",
    "The S-parameters bisection is done by time gating S11 and S22, taking the proper square root of the S21 corrected by return loss, and remixing the parameters according to the fixture signal flow graph. This method gives crude results but is robust.\n",
    "\n",
    "IEEE 370 recommends the following consistency tests:\n",
    "\n",
    "* Self de-embedding of 2x-Thru with absolute magnitude of residual insertion loss < ±0.1 dB and residual phase < ±1°\n",
    "* Compare the TDR of the fixture model to the FIX-DUT-FIX\n",
    " \n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dm_nzc = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru, name = '2xthru')\n",
    "nzc_fix1 = dm_nzc.s_side1\n",
    "nzc_fix1.name = 'nzc_FIX-1'\n",
    "nzc_fix2 = dm_nzc.s_side2\n",
    "nzc_fix2.name = 'nzc_FIX-2'\n",
    "nzc_d_dut = dm_nzc.deembed(fdf)\n",
    "nzc_d_dut.name = 'nzc_DUT'\n",
    "nzc_fix1_dc = IEEEP370.extrapolate_to_dc(nzc_fix1)\n",
    "nzc_d_dut_dc = IEEEP370.extrapolate_to_dc(nzc_d_dut)\n",
    "\n",
    "fig, ax = dm_nzc.plot_check_residuals()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The agreement between fixtures models and 2xthru is excellent, as shown by the magnitude and phase of residuals being much smaller than IEEEP370 ±0.1 dB and ±1° limits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = dm_nzc.plot_check_impedance(fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both fixtures show a close agreement with 2x-Thru from the start to the middle. However, the 5% artificial impedance mismatch added between 2x-Thru and FIX-DUT-FIX will deteriorate the DUT deembedding performance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Time Step')\n",
    "fdf_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dotted', color = 'm')\n",
    "nzc_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "dut_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].set_xlim((-2, 2))\n",
    "axs[0].legend(loc = 'lower left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "fdf.plot_s_db(0, 0, ax = axs[1], color = 'm', linestyle = 'dotted')\n",
    "nzc_d_dut.plot_s_db(0, 0, ax = axs[1], color = 'r')\n",
    "dut.plot_s_db(0, 0, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].set_ylim((-40, 5))\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The NZC deembedding algorithm has removed the delay of the fixtures. As expected, the impedance mismatch between 2x-Thru and FIX-DUT-FIX deteriorates the de-embedding by leaving an impedance bounce before time zero in the time domain. It is also visible as a ripple in the frequency domain."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### IEEEP370_SE_ZC_2xThru with impedance correction <a class=\"anchor\" id=\"IEEEP370_SE_ZC_2xThru-with-impedance-correction\"></a>\n",
    "This method takes 2x-Thru and the FIX-DUT-FIX as inputs. The algorithm computes the length of the fixtures by halving the delay of 2x-Thru in time domain transmission. The propagation constant `gamma` is also determined from the 2xThru. It then peels the FIX-DUT-FIX time domain impedance profile iteratively in cycles of determining start impedance and deembedding a single time sample long transmission line.\n",
    "\n",
    "Because the 2x-Thru is used only for length and propagation constant determination, the effect of an impedance mismatch with the FIX-DUT-FIX is reduced. If another FIX-DUT-FIX has to be de-embedded with the same fixtures, an impedance mismatch between the two FIX-DUT-FIX would have an adverse effect on deembedding performance.\n",
    "\n",
    "This method is more advanced than `IEEEP370_SE_NZC_2xThru` 2x-Thru S-parameters bisection. It has more options and often gives better results at the expense of more processing power."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dm_zc  = IEEEP370_SE_ZC_2xThru(dummy_2xthru = s2xthru, dummy_fix_dut_fix = fdf,\n",
    "                         bandwidth_limit = 10e9, pullback1 = 0, pullback2 = 0,\n",
    "                         leadin = 0, NRP_enable = False,\n",
    "                         name = 'zc2xthru')\n",
    "zc_d_dut = dm_zc.deembed(fdf)\n",
    "zc_d_dut.name = 'zc_DUT'\n",
    "zc_fix1 = dm_zc.s_side1\n",
    "zc_fix1.name = 'zc_FIX-1'\n",
    "zc_fix2 = dm_zc.s_side2\n",
    "zc_fix2.name = 'zc_FIX-2'\n",
    "zc_fix1_dc = IEEEP370.extrapolate_to_dc(zc_fix1)\n",
    "zc_d_dut_dc = IEEEP370.extrapolate_to_dc(zc_d_dut)\n",
    "\n",
    "fig, ax = dm_zc.plot_check_residuals()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The agreement between fixtures models and 2xthru is good, as shown by the magnitude and phase of residuals being smaller than IEEE 370 ±0.1 dB and ±1° limits.\n",
    "\n",
    "The residuals are higher than than with the NZC algorithm. This is the awaited result because the fixtures are modeled on the FIX-DUT-FIX impedance profile that has an artificially added impedance mismatch with the 2x-Thru."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = dm_zc.plot_check_impedance()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both fixtures show a close agreement with FIX-DUT-FIX from the start to the middle. The 2x-Thru was used for fixture length and propagation constant gamma determination only.\n",
    "\n",
    "The `leadin` option is useful if there is a need to capture several non-reference impedance samples before the time zero. The `pullback1` and `pullback2` options can shorten the fixtures by an integer number of time samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Time Step')\n",
    "fdf_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dotted', color = 'm')\n",
    "zc_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "dut_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].set_xlim((-1, 2))\n",
    "axs[0].set_ylim((15, 55))\n",
    "axs[0].legend(loc = 'lower left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "fdf.plot_s_db(0, 0, ax = axs[1], color = 'm', linestyle = 'dotted')\n",
    "zc_d_dut.plot_s_db(0, 0, ax = axs[1], color = 'r')\n",
    "dut.plot_s_db(0, 0, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].set_ylim((-40, 5))\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As expected, the ZC deembedding algorithm shows a better agreement with the reference DUT than NZC, both in the time and frequency domains. This is because it is not impacted by the impedance mismatch between 2xThru and FIX-DUT-FIX of this example. This impedance difference should be minimized as much as possible in practical designs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single Ended Comparison with AICC De-Embedding Utility <a class=\"anchor\" id=\"Single-Ended-Comparison-with-AICC-De-Embedding-Utility\"></a>\n",
    "A set of reference Matlab or Octave codes that implement the IEEEP370 NZC and ZC deembedding algorithms are available with an open-source BSD-3-Clause license [on the IEEE repository](https://opensource.ieee.org/elec-char/ieee-370/-/tree/master/TG1)\n",
    "\n",
    "However, not everyone has access to Matlab and RF Toolbox. Maybe, this is one of the reasons why you are reading this text,  looking forward to using `scikit-rf` and Python.\n",
    "\n",
    "A free compiled binary of Matlab routine with a GUI is available [on Amphenol website](https://www.amphenol-cs.com/software) with the name \"ACS De-embedding Utility\".\n",
    "<img src=\"ieeep370deembedding/AICC_Deembedding.png\">\n",
    "\n",
    "Let's compare the output of this tool on `scikit-rf` port of deembedding algorithms as a consistency check."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# read AICC generated files\n",
    "nzc_ref = rf.Network(directory + 'deembedded_SE_NZC_se_fdf.s2p')\n",
    "nzc_ref.name = 'aicc_nzc'\n",
    "zc_ref = rf.Network(directory + 'deembedded_SE_ZC_se_fdf.s2p')\n",
    "zc_ref.name = 'aicc_zc'\n",
    "nzc_ref_dc = IEEEP370.extrapolate_to_dc(nzc_ref)\n",
    "zc_ref_dc = IEEEP370.extrapolate_to_dc(zc_ref)\n",
    "\n",
    "# make purposely wrong NZC side 2 flip for comparison with AICC Tool\n",
    "nzc_wrong_d_dut = dm_nzc.s_side1.inv ** fdf ** dm_nzc.s_side2.inv\n",
    "nzc_wrong_d_dut.name = 'wrong flip'\n",
    "nzc_wrong_d_dut_dc = IEEEP370.extrapolate_to_dc(nzc_wrong_d_dut)\n",
    "\n",
    "# compute absolute differences\n",
    "# division of complex reflexion coeff is like substraction in dB and deg\n",
    "delta_nzc = nzc_ref / nzc_wrong_d_dut\n",
    "delta_zc = zc_ref / zc_d_dut\n",
    "\n",
    "# plot them all\n",
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('AICC Tool NZC side 1')\n",
    "nzc_wrong_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "nzc_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'g')\n",
    "nzc_ref_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].legend(loc = 'lower right')\n",
    "axs[0].set_xlim((-1, 2))\n",
    "\n",
    "axs[1].set_title('AICC Tool NZC side 2')\n",
    "nzc_wrong_d_dut_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'r')\n",
    "nzc_d_dut_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'g')\n",
    "nzc_ref_dc.plot_z_time_step(1, 1, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].legend(loc = 'lower right')\n",
    "axs[1].set_xlim((-1, 2))\n",
    "\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When comparing `scikit-rf` and `AICC` results, NZC algorithm exhibits a small discrepancy after the DUT section (green curve). This is because `AICC` FIX-2 has a wrong flip, which is demonstrated by the results of side 1 and side 2 being different for a symmetrical FIX-DUT-FIX (dashed black curve). If this error is propagated to `scikit-rf` for comparison's sake, the agreement is a perfect fit (red curve). The version of `AICC` used is 1.0.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# plot them all\n",
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('AICC Tool NZC side 1')\n",
    "\n",
    "axs[0].set_title('AICC Tool ZC side 1')\n",
    "zc_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "zc_ref_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].legend(loc = 'lower right')\n",
    "axs[0].set_xlim((-1, 2))\n",
    "\n",
    "axs[1].set_title('AICC Tool NZC side 2')\n",
    "zc_d_dut_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'r')\n",
    "zc_ref_dc.plot_z_time_step(1, 1, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].legend(loc = 'lower right')\n",
    "axs[1].set_xlim((-1, 2))\n",
    "\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When comparing `scikit-rf` and `AICC` results, ZC is visually a perfect fit in the time domain for both sides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Absolute magnitude difference')\n",
    "delta_nzc.plot_s_db(1,0, ax = axs[0])\n",
    "delta_zc.plot_s_db(1,0, ax = axs[0])\n",
    "axs[0].legend(loc = 'upper right')\n",
    "\n",
    "axs[1].set_title('Absolute phase difference')\n",
    "delta_nzc.plot_s_deg(1,0, ax = axs[1])\n",
    "delta_zc.plot_s_deg(1,0, ax = axs[1])\n",
    "axs[1].legend(loc = 'upper right')\n",
    "\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both NZC (with flip error) and ZC absolute differences of magnitude and phase are within ±0.06 dB and ±0.4° up to 10 GHz, this is excellent.\n",
    "\n",
    "In conclusion, for the single-ended case studied, `scikit-rf` gives consistent results with `AICC De-embedding Utility` implementation of IEEE 370 deembedding routines. It also solves the NZC FIX-2 flip error in `AICC De-embedding Utility` version 1.0.0."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mixed mode <a class=\"anchor\" id=\"Mixed-mode\"></a>\n",
    "ZC and NZC deembedding algorithms are compatible with differential 4-port networks.\n",
    "Single-ended to generalized mixed-modes transformation is used to separate differential and common modes subnetworks and model the fixture individually. The differential and common modes fixtures are then merged and transformed into a 4-port single-ended again. This approach assumes differential to common mode transformation is insignificant."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mixed mode simulation of 2xThru, DUT and Fixture-DUT-Fixture <a class=\"anchor\" id=\"Mixed-mode-simulation-of-2xThru,-DUT-and-Fixture-DUT-Fixture\"></a>\n",
    "We use [Qucs](http://qucs.sourceforge.net/) to simulate coupled microstrip line artifacts. This is a free simulator that can generate S-parameters of equation-based RF devices, among other things.\n",
    "\n",
    "* `diff_dut` is a a Beatty structure with a 3xWidth coupled microstrip section connected left and right by two uniform 1xWidth coupled microstrip lines.\n",
    "* `diff_fdf` is FIX-DUT-FIX, prolonging the DUT left and right with a coupled microstrip line and a planar-to-coaxial connector.\n",
    "* `diff_2xthru` is FIX-FIX and is the cascade of the two prolonging lines and connectors without the DUT. For example purpose, the width of the lines is altered to show the effect of impedance mismatch on the deembedding process.\n",
    "\n",
    "The target is to get the left and right fixtures models and retrieve the DUT by deembedding them from FIX-DUT-FIX.\n",
    "\n",
    "The files and `qucs` sources are located in the directory `ieeep370deembedding` next to this notebook.\n",
    "\n",
    "<img src=\"ieeep370deembedding/diff_fdf.png\"> "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the figure below, the time shift of DUT caused by the fixtures is visible on FIX-DUT-FIX trace. The 2x-Thru trace shows the awaited impedance mismatch with the FIX-DUT-FIX. The connectors add an impedance bump at the ends of 2x-Thru and FIX-DUT-FIX impedance step response. The reference DUT is the Beatty structure alone, without fixtures.\n",
    "\n",
    "The common mode impedances are significantly mismatched from 25 ohm with this coupled lines geometry."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# load single-ended data\n",
    "se_dut = rf.Network(directory + 'diff_dut.s4p')\n",
    "se_2xthru = rf.Network(directory + 'diff_2xthru_with_connectors.s4p')\n",
    "se_2xthru.name = 'diff_2xthru'\n",
    "#se_2xthru = se_2xthru.delay(10, 'ps', 3) # skew\n",
    "se_fdf = rf.Network(directory + 'diff_fdf_with_connectors.s4p')\n",
    "se_fdf.name = 'diff_fdf'\n",
    "#se_fdf = se_fdf.delay(10, 'ps', 0) # skew\n",
    "\n",
    "# transform to mixed-modes\n",
    "mm_dut = se_dut.copy()\n",
    "mm_dut.se2gmm(p = 2)\n",
    "mm_2xthru = se_2xthru.copy()\n",
    "mm_2xthru.se2gmm(p = 2)\n",
    "mm_fdf = se_fdf.copy()\n",
    "mm_fdf.se2gmm(p = 2)\n",
    "\n",
    "# extrapolate to DC for time step\n",
    "mm_dut_dc = IEEEP370.extrapolate_to_dc(mm_dut)\n",
    "mm_2xthru_dc = IEEEP370.extrapolate_to_dc(mm_2xthru)\n",
    "mm_fdf_dc = IEEEP370.extrapolate_to_dc(mm_fdf)\n",
    "\n",
    "# set True to write .s4p files\n",
    "if False:\n",
    "    connectors = rf.concat_ports([connector, connector], port_order = 'second')\n",
    "    se_2xthru = connectors ** rf.Network(directory + 'diff_2xthru.s4p') ** connectors\n",
    "    se_fdf = connectors ** rf.Network(directory + 'diff_fdf.s4p') ** connectors\n",
    "    se_2xthru.write_touchstone(directory + 'diff_2xthru_with_connectors.s4p')\n",
    "    se_fdf.write_touchstone(directory + 'diff_fdf_with_connectors.s4p')\n",
    "\n",
    "# looking at the newtorks\n",
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "\n",
    "axs[0].set_title('Time Step')\n",
    "mm_dut_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "mm_fdf_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "mm_2xthru_dc.plot_z_time_step(0, 0, ax = axs[0])\n",
    "mm_dut_dc.plot_z_time_step(2, 2, ax = axs[0])\n",
    "mm_fdf_dc.plot_z_time_step(2, 2, ax = axs[0])\n",
    "mm_2xthru_dc.plot_z_time_step(2, 2, ax = axs[0])\n",
    "axs[0].set_xlim((-2, 2))\n",
    "axs[0].legend(loc = 'center left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "mm_dut.plot_s_db(0, 0, ax = axs[1])\n",
    "mm_fdf.plot_s_db(0, 0, ax = axs[1])\n",
    "mm_2xthru.plot_s_db(0, 0, ax = axs[1])\n",
    "axs[1].legend(loc = 'lower left')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mixed mode S-parameters quality checking <a class=\"anchor\" id=\"Mixed-mode-S-parameters-quality-checking\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, check the quality metrics of the data to ensure they do not violate passivity, reciprocity, or causality. Initial quality checking in the frequency domain results in percentage scores and is informative only. The results are evaluated as good, acceptable, inconclusive or poor. Inconclusive or poor results may indicate the need to recalibrate the VNA and perform the measurements again.\n",
    "\n",
    "This quick check may raise potential issues that question measurement, simulation, or de-embedding process.\n",
    "\n",
    "Only the differential and the common modes are checked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fd_qm = IEEEP370_FD_QM()\n",
    "print(mm_2xthru.name)\n",
    "qm_2xthru = fd_qm.check_mm_quality(se_2xthru)\n",
    "fd_qm.print_qm(qm_2xthru)\n",
    "print(mm_fdf.name)\n",
    "qm_fdf = fd_qm.check_mm_quality(se_fdf)\n",
    "fd_qm.print_qm(qm_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the results are in the best class, which is not surprising for synthetic data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Application-based quality checking in the time domain targets interconnect applications with specified data rate and rise time. It is an advanced process that targets giving a physical estimation in millivolt based on peak distortion analysis between the input data and ideal re-constructed data.\n",
    "\n",
    "Only the differential and the common modes are checked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "td_qm = IEEEP370_TD_QM(1e9, # bps\n",
    "                       32,  # UI samples\n",
    "                       0.4, # rise-time in fraction of UI\n",
    "                       1,   # Gaussian pulse,\n",
    "                       2,   # zero padding extrapolation\n",
    "                       verbose = False)\n",
    "print(mm_2xthru.name)\n",
    "qm_2xthru = td_qm.check_mm_quality(se_2xthru)\n",
    "td_qm.print_qm(qm_2xthru)\n",
    "print(mm_fdf.name)\n",
    "qm_fdf = td_qm.check_mm_quality(se_fdf)\n",
    "td_qm.print_qm(qm_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the results are in the best class, which is not surprising for synthetic data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before exercising the deembedding algorithms, the compliance of input data with IEEE 370 fixture electrical requirements (FER) shall be verified."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fer = IEEEP370_FER()\n",
    "fig = fer.plot_fd_mm_fer(se_2xthru)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = fer.plot_td_mm_fer(se_2xthru, se_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The performance of the 2x-Thru is deemed class C compliant for frequencies below 10 GHz. This is due to the artificial impedance mismatch between 2x-Thru and FIX-DUT-FIX that fails the FER5 A and B limits."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### IEEEP370_MM_NZC_2xThru without impedance correction <a class=\"anchor\" id=\"IEEEP370_MM_NZC_2xThru-without-impedance-correction\"></a>\n",
    "This method takes the 2x-Thru as input. It is a simple and efficient bisection algorithm that cannot correct for the difference of impedance between the lines of FIX-FIX and FIX-DUT-FIX. This unwanted difference should be minimized. It can appear because of materials inhomogeneities, manufacturing processes, or if the artifacts are not built on the same board.\n",
    "\n",
    "The S-parameters bisection is done by time gating S11 and S22, taking the proper square root of the S21 corrected by return loss, and remixing the parameters according to the fixture signal flow graph. This method gives crude results but is robust.\n",
    "\n",
    "IEEE 370 recommends the following consistency tests:\n",
    "\n",
    "* Self de-embedding of 2x-Thru with absolute magnitude of residual insertion loss < ±0.1 dB and residual phase < ±1°\n",
    "\n",
    "* Compare the TDR of the fixture model to the FIX-DUT-FIX\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# de-embedding\n",
    "z0 = 50\n",
    "dm_mmnzc  = IEEEP370_MM_NZC_2xThru(dummy_2xthru = se_2xthru, z0 = z0, name = 'mmnzc')\n",
    "mm_nzc_side1 = dm_mmnzc.se_side1.copy()\n",
    "mm_nzc_side1.name = 'FIX-1-2'\n",
    "mm_nzc_side1.se2gmm(p = 2)\n",
    "se_d_dut_nzc = dm_mmnzc.deembed(se_fdf)\n",
    "se_d_dut_nzc.name = 'd_dut_nzc'\n",
    "mm_d_dut_nzc = se_d_dut_nzc.copy()\n",
    "mm_d_dut_nzc.se2gmm(p = 2)\n",
    "mm_nzc_side1_dc = IEEEP370.extrapolate_to_dc(mm_nzc_side1)\n",
    "mm_d_dut_nzc_dc = IEEEP370.extrapolate_to_dc(mm_d_dut_nzc)\n",
    "\n",
    "fig, ax = dm_mmnzc.plot_check_residuals()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The agreement between fixtures models and 2xthru is excellent, as shown by the magnitude and phase of residuals being much smaller than IEEEP370 ±0.1 dB and ±1° limits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = dm_mmnzc.plot_check_impedance(se_fdf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both fixtures show a close agreement with 2x-Thru from the start to the middle. However, the artificial impedance mismatch added between 2x-Thru and FIX-DUT-FIX will deteriorate the DUT deembedding performance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Time Step')\n",
    "mm_fdf_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dotted', color = 'm')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "mm_dut_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "mm_fdf_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dotted', color = 'b')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(2, 2, ax = axs[0], color = 'c')\n",
    "mm_dut_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].set_xlim((-2, 2))\n",
    "axs[0].legend(loc = 'center left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "mm_fdf.plot_s_db(0, 0, ax = axs[1], color = 'm', linestyle = 'dotted')\n",
    "mm_d_dut_nzc.plot_s_db(0, 0, ax = axs[1], color = 'r')\n",
    "mm_dut.plot_s_db(0, 0, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].set_ylim((-40, 5))\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The NZC deembedding algorithm has removed the delay of the fixtures. As expected, the impedance mismatch between 2x-Thru and FIX-DUT-FIX deteriorates the de-embedding by leaving an impedance bounce before time zero in the time domain. It is also visible as a ripple in the frequency domain."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### IEEEP370_MM_ZC_2xThru with impedance correction <a class=\"anchor\" id=\"IEEEP370_MM_ZC_2xThru-with-impedance-correction\"></a>\n",
    "This method take 2x-Thru and FIX-DUT-FIX as inputs. It makes a correction for the (unwanted) difference of impedance between the lines of FIX-FIX and FIX-DUT-FIX.\n",
    "\n",
    "The algorithm computes the length of the fixtures by halving the delay of 2x-Thru in time domain transmission. The propagation constant gamma is also determined from the 2xThru. It then peels the FIX-DUT-FIX time domain impedance profile iteratively in cycles of determining start impedance and deembedding a single time sample long transmission line.\n",
    "\n",
    "IEEEP370_MM_ZC_2xThru has more options and often give better results than IEEEP370_MM_NZC_2xThru, but it consumes more processing power."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# de-embedding\n",
    "dm_mmzc  = IEEEP370_MM_ZC_2xThru(dummy_2xthru = se_2xthru, dummy_fix_dut_fix = se_fdf,\n",
    "                         bandwidth_limit = 10e9, pullback1 = 0, pullback2 = 0,\n",
    "                         leadin = 0, NRP_enable = False, name = 'mmzc')\n",
    "mm_zc_side1 = dm_mmzc.se_side1.copy()\n",
    "mm_zc_side1.name = 'FIX-1-2'\n",
    "mm_zc_side1.se2gmm(p = 2)\n",
    "se_d_dut_zc = dm_mmzc.deembed(se_fdf)\n",
    "se_d_dut_zc.name = 'd_dut_zc'\n",
    "mm_d_dut_zc = se_d_dut_zc.copy()\n",
    "mm_d_dut_zc.se2gmm(p = 2)\n",
    "mm_zc_side1_dc = IEEEP370.extrapolate_to_dc(mm_zc_side1)\n",
    "mm_d_dut_zc_dc = IEEEP370.extrapolate_to_dc(mm_d_dut_zc)\n",
    "\n",
    "fig, ax = dm_mmzc.plot_check_residuals()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The agreement between fixtures models and 2xthru is good, as shown by the magnitude and phase of residuals differential mode being smaller than IEEE 370 ±0.1 dB and ±1° limits. The common mode residuals are within ±0.3 dB and ±6° which is higher but not catastrophic.\n",
    "\n",
    "The residuals are higher than than with the NZC algorithm. This is the awaited result because the fixtures are modeled on the FIX-DUT-FIX impedance profile that has an artificially added impedance mismatch with the 2x-Thru. This is especially true for common mode."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = dm_mmzc.plot_check_impedance()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both fixtures show a close agreement with FIX-DUT-FIX from the start to the middle. The 2x-Thru was used for fixture length and propagation constant gamma determination only.\n",
    "\n",
    "The `leadin` option is useful if there is a need to capture several non-reference impedance samples before the time zero. The `pullback1` and `pullback2` options can shorten the fixtures by an integer number of time samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Time Step')\n",
    "mm_fdf_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dotted', color = 'm')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "mm_dut_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "mm_fdf_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dotted', color = 'b')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(2, 2, ax = axs[0], color = 'c')\n",
    "mm_dut_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].set_xlim((-2, 2))\n",
    "axs[0].legend(loc = 'center left')\n",
    "\n",
    "axs[1].set_title('Frequency')\n",
    "mm_fdf.plot_s_db(0, 0, ax = axs[1], color = 'm', linestyle = 'dotted')\n",
    "mm_d_dut_zc.plot_s_db(0, 0, ax = axs[1], color = 'r')\n",
    "mm_dut.plot_s_db(0, 0, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].set_ylim((-40, 5))\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As expected, the ZC deembedding algorithm shows a better agreement with the reference DUT than NZC, both in the time and frequency domains. This is because it is not impacted by the impedance mismatch between 2xThru and FIX-DUT-FIX of this example. This impedance difference should be minimized as much as possible in practical designs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mixed mode comparison with AICC De-Embedding Utility <a class=\"anchor\" id=\"Mixed-mode-comparison-with-AICC-De-Embedding-Utility\"></a>\n",
    "Let's compare the deembedded DUTs from `scikit-rf` and `AICC De-Embedding Utility` to conclude this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# load single-ended data\n",
    "se_ref_nzc = rf.Network(directory + 'deembedded_MM_NZC_diff_fdf_with_connectors.s4p')\n",
    "se_ref_nzc.name = 'aicc_nzc'\n",
    "se_ref_zc = rf.Network(directory + 'deembedded_MM_ZC_diff_fdf_with_connectors.s4p')\n",
    "se_ref_zc.name = 'aicc_zc'\n",
    "\n",
    "# transform to mixed-modes\n",
    "mm_ref_nzc = se_ref_nzc.copy()\n",
    "mm_ref_nzc.se2gmm(p = 2)\n",
    "mm_ref_zc = se_ref_zc.copy()\n",
    "mm_ref_zc.se2gmm(p = 2)\n",
    "\n",
    "# extrapolate to DC for time step\n",
    "mm_ref_nzc_dc = IEEEP370.extrapolate_to_dc(mm_ref_nzc)\n",
    "mm_ref_zc_dc = IEEEP370.extrapolate_to_dc(mm_ref_zc)\n",
    "\n",
    "# make purposely wrong NZC side 2 flip for comparison with AICC Tool\n",
    "se_nzc_wrong_d_dut = dm_mmnzc.se_side1.inv ** se_fdf ** dm_mmnzc.se_side2.inv\n",
    "mm_nzc_wrong_d_dut = se_nzc_wrong_d_dut.copy()\n",
    "mm_nzc_wrong_d_dut.se2gmm(p=2)\n",
    "mm_nzc_wrong_d_dut.name = 'wrong flip'\n",
    "mm_nzc_wrong_d_dut_dc = IEEEP370.extrapolate_to_dc(mm_nzc_wrong_d_dut)\n",
    "\n",
    "# compute absolute differences\n",
    "# division of complex reflexion coeff is like substraction in dB and deg\n",
    "delta_nzc = mm_ref_nzc / mm_nzc_wrong_d_dut\n",
    "delta_zc = mm_ref_zc / mm_d_dut_zc\n",
    "\n",
    "# plot them all\n",
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "\n",
    "axs[0].set_title('AICC Tool NZC side 1')\n",
    "mm_nzc_wrong_d_dut_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'g')\n",
    "mm_ref_nzc_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "mm_nzc_wrong_d_dut_dc.plot_z_time_step(2, 2, ax = axs[0], color = 'b')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(2, 2, ax = axs[0], color = 'c')\n",
    "mm_ref_nzc_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].legend(loc = 'center right')\n",
    "axs[0].set_xlim((-1, 2))\n",
    "\n",
    "axs[1].set_title('AICC Tool NZC side 2')\n",
    "mm_nzc_wrong_d_dut_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'r')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'g')\n",
    "mm_ref_nzc_dc.plot_z_time_step(1, 1, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "mm_nzc_wrong_d_dut_dc.plot_z_time_step(3, 3, ax = axs[1], color = 'b')\n",
    "mm_d_dut_nzc_dc.plot_z_time_step(3, 3, ax = axs[1], color = 'c')\n",
    "mm_ref_nzc_dc.plot_z_time_step(3, 3, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].legend(loc = 'center right')\n",
    "axs[1].set_xlim((-1, 2))\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When comparing `scikit-rf` and `AICC` results, NZC algorithm exhibits a small discrepancy after the DUT section (green curve). This is because `AICC` FIX-2 has a wrong flip, which is demonstrated by the results of side 1 and side 2 being different for a symmetrical FIX-DUT-FIX (dashed black curve). If this error is propagated to `scikit-rf` for comparison's sake, the agreement is a perfect fit (red curve). The version of `AICC` used is 1.0.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('AICC Tool NZC side 1')\n",
    "\n",
    "axs[0].set_title('AICC Tool ZC side 1')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(0, 0, ax = axs[0], color = 'r')\n",
    "mm_ref_zc_dc.plot_z_time_step(0, 0, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(2, 2, ax = axs[0], color = 'c')\n",
    "mm_ref_zc_dc.plot_z_time_step(2, 2, ax = axs[0], linestyle = 'dashed', color = 'k')\n",
    "axs[0].legend(loc = 'center right')\n",
    "axs[0].set_xlim((-1, 2))\n",
    "\n",
    "axs[1].set_title('AICC Tool NZC side 2')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(1, 1, ax = axs[1], color = 'r')\n",
    "mm_ref_zc_dc.plot_z_time_step(1, 1, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "mm_d_dut_zc_dc.plot_z_time_step(3, 3, ax = axs[1], color = 'c')\n",
    "mm_ref_zc_dc.plot_z_time_step(3, 3, ax = axs[1], linestyle = 'dashed', color = 'k')\n",
    "axs[1].legend(loc = 'center right')\n",
    "axs[1].set_xlim((-1, 2))\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When comparing `scikit-rf` and `AICC` results, ZC is visually a perfect fit in the time domain for both sides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(8, 4))\n",
    "axs[0].set_title('Absolute magnitude difference')\n",
    "delta_nzc.plot_s_db(1, 0, ax = axs[0])\n",
    "delta_nzc.plot_s_db(3, 2, ax = axs[0])\n",
    "delta_zc.plot_s_db(1, 0, ax = axs[0])\n",
    "delta_zc.plot_s_db(3, 2, ax = axs[0])\n",
    "axs[0].legend(loc = 'upper right')\n",
    "\n",
    "axs[1].set_title('Absolute phase difference')\n",
    "delta_nzc.plot_s_deg(1, 0, ax = axs[1])\n",
    "delta_nzc.plot_s_deg(3, 2, ax = axs[1])\n",
    "delta_zc.plot_s_deg(1, 0, ax = axs[1])\n",
    "delta_zc.plot_s_deg(3, 2, ax = axs[1])\n",
    "axs[1].legend(loc = 'upper right')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The agreement between `scikit-rf` and `AICC De-Embedding Utility` is better than ±0.05 dB and ±0.5° for the NZC algorithm. For ZC it keep within ±0.4 dB and ±2° on full bandwidth, that is good enought.\n",
    "\n",
    "In conclusion, for the specific mixed mode case studied, we can say that `scikit-rf` gives consistent results with `AICC De-embedding Utility` implementation in IEEEP370 deembedding routines. It also solves the NZC FIX-2 flip error in `AICC De-embedding Utility` version 1.0.0."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  },
  "vscode": {
   "interpreter": {
    "hash": "637c323cf467337602e9974a89cb4d3fc95fac3ef875a73e62754f8e768d8de7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
